﻿#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
using namespace std;

//一、多态的概念 

//多态的概念：通俗来说，就是多种形态，具体点就是去完成某个行为，当不同的对象去完成时会
//产生出不同的状态。

//二、多态的定义及实现 

//2.1多态的构成条件

//多态是在不同继承关系的类对象，去调用同一函数，产生了不同的行为。比如Student继承了
//Person。Person对象买票全价，Student对象买票半价。
//那么在继承中要构成多态还有两个条件：
//1. 必须通过基类的指针或者引用调用虚函数
//2. 被调用的函数必须是虚函数，且派生类必须对基类的虚函数进行重写（调用函数是重写的虚函数）


//2.2 虚函数 

//虚函数：即被virtual修饰的类成员函数称为虚函数
class Person 
{
public:
	virtual void BuyTicket() 
	{ 
		cout << "买票->全价" << endl; 
	}
};
//注意：只有成员函数才能变成虚函数，全局的函数是不可以变成虚函数的

//2.3虚函数的重写 

//虚函数的重写(覆盖)：派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的
//返回值类型、函数名字、参数列表（这个参数指的是类型，跟形参和缺省值等无关）完全相同)，称子类的虚函数重写了基类的虚函数。
class Person 
{
public:
	virtual void BuyTicket() 
	{ 
		cout << "买票->全价" << endl;
	}
};
class Student : public Person 
{
public:
	virtual void BuyTicket() 
	{ 
		cout << "买票->半价" << endl;
	}
	//注意：在重写基类虚函数时，派生类的虚函数在不加virtual关键字时，虽然也可以构成重写(因
   //为继承后基类的虚函数被继承下来了在派生类依旧保持虚函数属性),但是该种写法不是很规范，不建议
   //这样使用
   /*void BuyTicket() { cout << "买票-半价" << endl; }*/
};
//多态：不同的成员传过去，调用不同的函数
//多态调用看的是指向的对象
//普通对象看的是当前对象的类型
void funcbuyticket(Person& p)
{
	p.BuyTicket();
}
void test1()
{
	Person P;
	Student st;
	funcbuyticket(P);
	funcbuyticket(st);
}

//虚函数重写的两个例外：
//1.协变(基类与派生类虚函数返回值类型不同)
//派生类重写基类虚函数时，与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指
//针或者引用，派生类虚函数返回派生类对象的指针或者引用时，称为协变
class A 
{
};
class B : public A 
{
};
class Person 
{
public:
	virtual A* f() 
	{ 
		return new A; 
	}
};
class Student : public Person 
{
public:
	virtual B* f() 
	{ 
		return new B;
	}
};
//注意：这里必须同时为指针或引用，不能一个是指针一个是引用

//2. 析构函数的重写(基类与派生类析构函数的名字不同)
//如果基类的析构函数为虚函数，此时派生类析构函数只要定义，无论是否加virtual关键字，
//都与基类的析构函数构成重写，虽然基类与派生类析构函数名字不同。虽然函数名不相同，
//看起来违背了重写的规则，其实不然，这里可以理解为编译器对析构函数的名称做了特殊处
//理，编译后析构函数的名称统一处理成destructor
class Person 
{
public:
	virtual ~Person() 
	{ 
		cout << "~Person()" << endl; 
	}
};
class Student : public Person 
{
public:
	virtual ~Student() 
	{ 
		cout << "~Student()" << endl;
	}
};
//只有派生类Student的析构函数重写了Person的析构函数，下面的delete对象调用析构函
//数，才能构成多态，才能保证p1和p2指向的对象正确的调用析构函数。
int main()
{
	Person* p1 = new Person;
	delete p1;

	Person* p2 = new Student;
	delete p2;//这里调用p2->destructor()+operator delete(p)

	//这里的p2我们期望p2->destructor()是一个多态调用，而不是普通调用
	//如果这里我们不进行虚函数的重写形成多态，那么就有可能会导致内存泄漏

	return 0;
}
//总结：由于派生类可以省略virtual，所以基类的析构函数尽量加上virtual，形成虚函数
//也就是只要你想被继承的类，尽量都加上virtual，形成虚函数

//注意：
//虚函数的一些细节：
//1.派生类的重写的虚函数可以不加virtual（子类可以不加virtual，但是父类必须加virtual）
//2.派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同
//3.协变，返回值可以不同，但是要求返回值必须是父子关系的指针和引用

//2.4 C++11 override和ﬁnal 

//从上面可以看出，C++对函数重写的要求比较严格，但是有些情况下由于疏忽，可能会导致函数
//名字母次序写反而无法构成重载，而这种错误在编译期间是不会报出的，只有在程序运行时没有
//得到预期结果才来debug会得不偿失，因此：C++11提供了override和ﬁnal两个关键字，可以帮
//助用户检测是否重写。

//1. ﬁnal：修饰虚函数，表示该虚函数不能再被重写
class c
{
public:
	virtual void print() final
	{
		cout << "handsome" << endl;
	}
};
class d :public c
{
public:
	virtual void print()//这里不能进行重写
	{
		cout << "handsome" << endl;
	}
};

//2. override: 检查派生类虚函数是否重写了基类某个虚函数，如果没有重写编译报错
class e
{
public:
	virtual void sum()
	{
		cout << "sum" << endl;
	}
};
class f :public e
{
public:
	virtual void sum()override
	{
		cout << "sum" << endl;
	}
};

//补充：如果我们不想让一个类被继承，有三个方法：
//方法一、把基类的构造函数进行私有
class g
{
public:
	//因为我们将构造函数给私有了，所以我们要写一个函数在类里面调用构造函数
	static g createobj()
	{
		return g();
	}
private:
	g()
	{
	}
};
void test()
{
	g::createobj();
}
class h : public g
{
	h()
	{

	}
};
//方法二、把基类的析构函数进行私有化，让派生类调用不了
class car
{
public:
	//写一个destory函数
	void destory()
	{

	}
private:
	~car()
	{

	}
};
//如何构造和析构car类呢
void createcar()
{
	car* car1 =new car;
	//析构直接调用car类的destory函数
	car1->destory();
}
class baoma : public car
{
public:
	baoma()
	{

	}
};
//方法三、基类加一个final,使其称为最终类
class i final
{
public:

private:

};
class j : public i
{
public:

private:

};

//总结：final有两个作用：final修饰虚函数那么虚函数就不能被重写，final修饰类，类就不能被继承

//2.5 重载、覆盖(重写)、隐藏(重定义)的对比

//重载：两个函数位于同一个作用域之内，并且两个函数的函数名相同，参数不同

//覆盖（重写）：两个函数分别位于基类和派生类之中，函数名、参数和返回类型三个都相同（协变除外），
//并且两个函数必须是虚函数

//隐藏（重定义）：两个函数分别位于基类和派生类之中，函数名相同即可，也就是两个基类和派生类的同名函数不构成重写就是重定义


//三、抽象类

//3.1 概念 

//在虚函数的后面写上 = 0 ，则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类（也叫接口
//类），抽象类不能实例化出对象。派生类继承后也不能实例化出对象，只有重写纯虚函数，派生
//类才能实例化出对象。纯虚函数规范了派生类必须重写，另外纯虚函数更体现出了接口继承。
//抽象类在现实世界中没有实体
class Date
{
public:
	virtual void year() = 0;
};
class M :public Date
{
};
void testdate()
{
	Date d1;//无法实例化对象，因为这是抽象类
	M Date;//这里也无法实例化出对象
}
//如果想要实例化出对象就要在派生类中进行重写
class n
{
public:
	virtual void print() = 0;
};
class v :public n
{
public:
	virtual void print()
	{
		cout << "print" << endl;
	}
};
void test4()
{
	n n1;//不能实例化处对象
	v v1;//可以实例化出对象，因为进行了重写
}
//虽然不能抽象类不能创建出对象，但是可以定义一个指针
class Car
{
public:
	virtual void Drive() = 0;//没有虚表
};
class Benz :public Car
{
public:
	virtual void Drive()
	{
		cout << "Benz-舒适" << endl;
	}
};
class BMW :public Car
{
public:
	virtual void Drive()
	{
		cout << "BMW-操控" << endl;
	}
};
void print(Car* car)
{
	car->Drive();
}
void Test()
{
	print(new Benz);
	Car* pBMW = new BMW;
	pBMW->Drive();
}
//注意：
//这里的纯虚函数是强制让派生类重写虚函数，并且纯虚函数在父类
//而override是检查是否完成重写，override在子类


//四、多态的原理及虚函数的理解

//4.1虚函数表
//下面这个类sizeof(Base)是多少？
class Base
{
public:
	virtual void Func1()
	{
		cout << "Func1()" << endl;
	}
private:
	int _b = 1;
};
void test2()
{
	Base b;
}
//通过观察测试我们发现b对象是8字节，除了_b成员，还多一个__vfptr放在对象的前面
//对象中的这个指针我们叫做虚函数表指针(v代表virtual，f代表function)。
//一个含有虚函数的类中都至少都有一个虚函数表指针，因为虚函数的地址要被放到虚函数表中，虚函数表也简称虚表

class Person 
{
public:
	virtual void BuyTicket() 
	{
		cout << "买票-全价" << endl; 
	}
	void func1()
	{
	}
	void func2() 
	{
	}
protected:
	int _a = 0;
};
class Student : public Person 
{
public:
	virtual void BuyTicket()
	{ 
		cout << "买票-半价" << endl; 
	}
protected:
	int _b = 1;
};
void Func(Person& p)
{
	p.BuyTicket();
}
int main()
{
	Person ps;
	Student st;

	ps = st;
	Person* p = &st;
	Person& ref = st;

	//打印地址验证虚表放在静态区还是常量区
	//由于同区域的地址不会偏离太远，那么直接打印地址验证
	int a = 0;
	printf("栈区：%p\n", &a);

	static int b = 1;
	printf("静态区：%p\n", &b);

	int* pp = new int;
	printf("堆区:%p\n", pp);

	const char* str = "i love you";
	printf("常量区（代码段）：%p\n", str);

	//打印虚表的地址
	printf("虚表：%p\n", *((int*)(&ps)));
	printf("虚表：%p\n", *((int*)(&st)));
	return 0;
}


//4.2函数指针数组打印虚表
typedef void(*func_ptr) ();
//void printvtf(func_ptr* table)
void printvtf(func_ptr table[])
{
	for (size_t i = 0;table[i] != nullptr; i++)
	{
		printf("[%d]:%p", i, table[i]);
		func_ptr f = table[i];
		f();
	}
	printf("\n");
}
//打印两个对象的虚表的地址
void printaddress()
{
	Person p1;
	Student st1;

	int ptr1 = *((int*)&p1);
	printvtf((func_ptr*)ptr1);

	int ptr2 = *((int*)&st1);
	printvtf((func_ptr*)ptr2);

}

//4.3多态的分类

//（1）静态（编译时）的多态：函数重载，传不同的参数调用不同的函数，例如cout，cin
//（2）动态的（运行时）多态：通过继承、虚函数的重写形成的多态，传不同的对象调用不同的函数

//五、单继承和多继承中的虚函数表

//5.1单继承中的虚函数表
class Person
{
public:
	virtual void BuyTicket()
	{
		cout << "买票-全价" << endl;
	}
	void func1()
	{
	}
	void func2()
	{
	}
protected:
	int _a = 0;
};
class Student : public Person
{
public:
	virtual void BuyTicket()
	{
		cout << "买票-半价" << endl;
	}
protected:
	int _b = 1;
};
void Func(Person& p)
{
	p.BuyTicket();
}
//总结：
//1.单继承中的虚表派生类的虚表会把子类的虚函数拷贝过来，然后进行重写
//2.派生类的虚函数在虚表中紧跟起虚函数之后

//5.2多继承虚函数表
class Base1 
{
public:
	virtual void func1() 
	{ 
		cout << "Base1::func1" << endl; 
	}
	virtual void func2() 
	{ 
		cout << "Base1::func2" << endl; 
	}
private:
	int b1;
};
class Base2 
{
public:
	virtual void func1() 
	{ 
		cout << "Base2::func1" << endl; 
	}
	virtual void func2() 
	{ 
		cout << "Base2::func2" << endl; 
	}
private:
	int b2;
};
class Derive : public Base1, public Base2 
{
public:
	virtual void func1() 
	{ 
		cout << "Derive::func1" << endl; 
	}
	virtual void func3() 
	{ 
		cout << "Derive::func3" << endl; 
	}
private:
	int d1;
};
//函数指针数组打印虚表
typedef void(*func_ptr) ();
//void printvtf(func_ptr* table)
void printvtf(func_ptr table[])
{
	for (size_t i = 0;table[i] != nullptr; i++)
	{
		printf("[%d]:%p", i, table[i]);
		func_ptr f = table[i];
		f();
	}
	printf("\n");
}
int main()
{
	Derive d;
	//取第一张虚表
	int ptr1 = *((int*)&d);
	//取第二张虚表
	int ptr2 = *((int*)((char*) &d + sizeof(Base1)));
	printvtf((func_ptr*)ptr1);
	printvtf((func_ptr*)ptr2);

	//求Derive的字节大小
	cout << sizeof(d) << endl;
	return 0;
}



//六、多态练习题：

//以下程序输出结果是什么？
class A
{
public:
	virtual void func(int val = 1) 
	{ 
		std::cout << "A->" << val << std::endl; 
	}
	virtual void test()
	{ 
		func(); 
	}
};
class B : public A
{
public:
	void func(int val = 0) 
	{ 
		std::cout << "B->" << val << std::endl; 
	}
};

int main(int argc, char* argv[])
{
	B* p = new B;
	p->test();
	return 0;
}
//A: A->0 B: B->1 C: A->1 D: B->0 E: 编译出错 F: 以上都不正确
//解析：答案选择B，这里的p->test()，满足调用父类的指针，这里的func()同时也满足虚函数的重写
//注意：虚函数的三同中的参数列表相同指的是参数的类型，而不是指缺省参数和参数等相同，所以这里
//是构成虚函数的重写，所以这里是多态，但是由于这里给的是B类对象，所以调用B类中的func()，
//由于虚函数的重写只是重写虚函数的实现，所以在B类中的虚函数func用的函数声明还是A类中的func的声明
//所以这里B类中的缺省参数用的是A的缺省参数

//总结：这里总结出一点就是虚函数的重写重写的是虚函数的实现

//将上面这个题目变式一下：
//以下程序输出结果是什么？
class A
{
public:
	virtual void func(int val = 1)
	{
		std::cout << "A->" << val << std::endl;
	}
};
class B : public A
{
public:
	void func(int val = 0)
	{
		std::cout << "B->" << val << std::endl;
	}
	virtual void test()
	{
		func();
	}
};

int main(int argc, char* argv[])
{
	B* p = new B;
	p->test();
	return 0;
}
//A: A->0 B: B->1 C: A->1 D: B->0 E: 编译出错 F: 以上都不正确

//解析：答案选择D，此时就不是多态的调用了，他调用的就是自己的func，此时就是一个普通函数的调用了


//1. 什么是多态？答：多态的分类：1.静态的多态（函数重载）  2.动态的多态（继承中父类虚函数的重写+父类指针的调用）
//2. 什么是重载、重写(覆盖)、重定义(隐藏)？答：
//重载：两个函数位于同一个作用域之内，并且两个函数的函数名相同，参数不同
//覆盖（重写）：两个函数分别位于基类和派生类之中，函数名、参数和返回类型三个都相同（协变除外），
//并且两个函数必须是虚函数
//隐藏（重定义）：两个函数分别位于基类和派生类之中，函数名相同即可，也就是两个基类和派生类的同名函数不构成重写就是重定义
//3. 多态的实现原理？答：1.静态多态：函数名修饰规则  2.动态的多态：虚函数表的调用
//4. inline函数可以是虚函数吗？答：可以，不过编译器就忽略inline属性，这个函数就不再是
//inline，因为虚函数要放到虚表中去。
//5. 静态成员可以是虚函数吗？答：不能，因为静态成员函数没有this指针，使用类型::成员函数
//的调用方式无法访问虚函数表，所以静态成员函数无法放进虚函数表。
//6. 构造函数可以是虚函数吗？答：不能，因为对象中的虚函数表指针是在构造函数初始化列表
//阶段才初始化的。
//7. 析构函数可以是虚函数吗？什么场景下析构函数是虚函数？答：可以，并且最好把基类的析
//构函数定义成虚函数。参考本节课件内容
//8. 对象访问普通函数快还是虚函数更快？答：首先如果是普通对象，是一样快的。如果是指针
//对象或者是引用对象，则调用的普通函数快，因为构成多态，运行时调用虚函数需要到虚函
//数表中去查找。
//9. 虚函数表是在什么阶段生成的，存在哪的？答：虚函数表是在编译阶段就生成的，一般情况
//下存在代码段(常量区)的。
//10. C++菱形继承的问题？虚继承的原理？答：参考继承课件。注意这里不要把虚函数表和虚基
//表搞混了。
//11. 什么是抽象类？抽象类的作用？答：参考（3.抽象类）。抽象类强制重写了虚函数，另外抽
//象类体现出了接口继承关系。

